﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Services.MSBuild.Tasks.Core;
using Microsoft.Build.Framework;
using System.Collections;
using System.IO;
using System.Xml.XPath;
using System.Xml;
using System.Globalization;
using Microsoft.Build.Utilities;
using Microsoft.Services.NuGet;

namespace Microsoft.Services.MSBuild.Tasks
{
    public sealed class GetNuGetReferences
        : TaskBase
    {
        [Required]
        public ITaskItem CacheRoot
        { get; set; }

        [Required]
        public ITaskItem PackagesConfig
        { get; set; }

        [Required]
        public ITaskItem[] PackageStores
        { get; set; }

        [Output]
        public ITaskItem[] AssemblyPaths
        { get; set; }

        public override bool Execute()
        {
            ArrayList list = new ArrayList();

            foreach (PackageSpec packageSpec in GetPackageSpecs(this.PackagesConfig.ItemSpec))
            {
                string packageName = string.Format("{0}.{1}", packageSpec.Id, packageSpec.Version);
                string packagePath = Path.Combine(this.CacheRoot.ItemSpec, packageName, "lib");

                if (!Path.IsPathRooted(packagePath) || packagePath.StartsWith(Path.DirectorySeparatorChar.ToString()))
                {
                    packagePath = Path.GetFullPath(packagePath);
                }

                this.Log.LogMessage("Target directory: {0}", packagePath);

                if (Directory.Exists(packagePath))
                {
                    this.Log.LogMessage("Resolved existing NuGet reference path: {0}", packagePath);

                    list.Add(new TaskItem(packagePath));

                    continue;
                }

                IPackage package = null;

                foreach (ITaskItem packageStore in this.PackageStores)
                {
                    if (!string.IsNullOrEmpty(packageStore.ItemSpec))
                    {
                        package = this.GetPackage(packageSpec, packageStore.ItemSpec);
                    }

                    if (package != null)
                    {
                        this.Log.LogMessage("Downloaded package '{0}' (Ver. {1}) from {2}", packageSpec.Id, packageSpec.Version, packageStore.ItemSpec);

                        break;
                    }
                }

                if (package != null)
                {
                    if (!Directory.Exists(packagePath))
                    {
                        Directory.CreateDirectory(packagePath);
                    }

                    foreach (IPackageFile packageFile in package.GetFiles("lib"))
                    {
                        string packageFileName = Path.GetFileName(packageFile.Path);
                        string packageFilePath = Path.Combine(packagePath, packageFileName);

                        if (!File.Exists(packageFilePath))
                        {
                            this.Log.LogMessage("Extracting file '{0}' to '{1}'", packageFile.Path, packageFilePath);

                            using (Stream source = packageFile.GetStream())
                            using (Stream target = File.Create(packageFilePath))
                            {
                                source.CopyTo(target);
                            }
                        }
                    }

                    this.Log.LogMessage("Resolved new NuGet reference path: {0}", packagePath);

                    list.Add(new TaskItem(packagePath));
                }
            }

            this.AssemblyPaths = (ITaskItem[])list.ToArray(typeof(ITaskItem));

            return !base.Log.HasLoggedErrors;
        }

        private IEnumerable<PackageSpec> GetPackageSpecs(string packagesConfigPath)
        {
            this.Log.LogMessage("Parsing packages config: {0}", packagesConfigPath);

            FileInfo packagesConfig = new FileInfo(packagesConfigPath);

            if (packagesConfig.Exists)
            {
                XPathDocument cfg = new XPathDocument(packagesConfig.OpenRead());

                XPathNavigator nav = cfg.CreateNavigator();

                XPathNodeIterator ite = nav.Select("/packages/package");

                while (ite.MoveNext())
                {
                    PackageSpec spec = new PackageSpec()
                    {
                        Id = ite.Current.Evaluate("string(@id)") as string,
                        Version = new Version(ite.Current.Evaluate("string(@version)") as string)
                    };

                    yield return spec;
                }
            }
        }

        private IPackage GetPackage(PackageSpec packageSpec, string packageStoreUrl)
        {
            IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(new PackageSource(packageStoreUrl));

            return packageRepository.FindPackage(packageSpec.Id, packageSpec.Version.ToString());
        }

        private struct PackageSpec
        {
            public string Id;

            public Version Version;
        }
    }
}
